/*!
 *	\file myfs_inode.c
 *
 *	в данном файле хранятся функции для работы с и-узлами
 *
 */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/sys.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>

#include "myfs.h"
#include "myfs_inode.h"
#include "myfs_ialloc.h"

static struct myfs_inode *myfs_get_inode(struct super_block *sb, unsigned int ino, struct buffer_head **bhp);

typedef struct {
	__le32	*p;
	__le32	key;
	struct buffer_head *bh;
} Indirect;

static inline void add_chain(Indirect *p, struct buffer_head *bh, __le32 *v)
{
	p->key = *(p->p = v);
	p->bh = bh;
}

static int myfs_block_to_path(struct inode *inode, long block, int offsets[1])
{
	int n = 0;
	if(block < 0)
	{
		printk(KERN_ALERT "myfs: block number less than zero (%lu).\n", block);
	}
	BUG_ON(block != 0);
	offsets[n++] = block;
	return n;
}

static inline Indirect *myfs_get_branch(struct inode *inode, int depth, int *offsets, Indirect chain[1], int *err)	
{
	Indirect *p = chain;
	*err = 0;

	add_chain(chain, NULL, MYFS_I(inode)->i_data + *offsets);
	printk(KERN_ALERT "myfs: added the block number %d.\n", *(MYFS_I(inode)->i_data + *offsets));
	if (!p->key)
		goto no_block;
	while (--depth) 
	{
		BUG();
	}
	return NULL;
no_block:
	return p;
}

static int myfs_get_blocks(struct inode *inode, sector_t block, unsigned long maxblocks, struct buffer_head *bh, int create)
{
	int err = -EIO;
	int offsets[1];
	Indirect chain[1];
	Indirect *partial;
	int depth = 0;

	depth = myfs_block_to_path(inode, block, offsets);
	if (!depth)
		return err;

	partial = myfs_get_branch(inode, depth, offsets, chain, &err);

	if (!partial)
	{
		map_bh(bh, inode->i_sb, chain[depth -1].key);
		partial = chain+depth-1;
		goto cleanup;
	}
cleanup:
	while (partial > chain)
	{
		brelse(partial->bh);
		partial --;
	}
	return err;
}

int myfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh, int create)
{
	unsigned maxblocks = bh->b_size / inode->i_sb->s_blocksize;
	int ret = myfs_get_blocks(inode, block, maxblocks, bh, create);
	if(ret > 0)
	{
		bh->b_size = (ret * MYFS_DFLT_BLOCKSIZE);
	}
	return ret;
}

/*int myfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh, int create)
{
	return get_block(inode, block, bh, create);
}*/

/*static int myfs_readpages(struct file *file, struct address_space *mapping,
		struct list_head *pages, unsigned nr_pages)
{
	return mpage_readpages(mapping, pages, nr_pages, myfs_get_block);
}*/

/*!
	функция чтения страницы 
*/
static int myfs_readpage(struct file *file, struct page *page)
{
	return block_read_full_page(page, myfs_get_block);
}

/*!
	функция возвращает и-узел VFS, инициализированный соответствующими полями
	и-узла myfs, прочитанного с диска	 
*/
struct inode *myfs_iget(struct super_block *sb, unsigned int ino)
{
	struct myfs_inode_info *msi;
	struct myfs_inode *raw_inode;
	struct buffer_head *bh;
	struct inode *inode;

	printk(KERN_ALERT "myfs: trying to get new inode for inode number (%u).\n", ino);

	inode = iget_locked(sb, ino);
	if (!inode)
	{
		return -ENOMEM;
	}

	msi = MYFS_I(inode);

	raw_inode = myfs_get_inode(inode->i_sb, ino, &bh);
	if (!raw_inode)
	{
		return NULL;
	}

	inode->i_mode = le32_to_cpu(raw_inode->i_mode);
	inode->i_uid = le32_to_cpu(raw_inode->i_uid);
	inode->i_gid = le32_to_cpu(raw_inode->i_gid);
	inode->i_size = le32_to_cpu(raw_inode->i_size);
	inode->i_nlink = le32_to_cpu(raw_inode->i_num_links);
	inode->i_atime = (raw_inode->atime);
	inode->i_ctime = (raw_inode->ctime);
	inode->i_mtime = (raw_inode->mtime);

	msi->i_data[0] = raw_inode->data[0];

	if (S_ISREG(inode->i_mode))
	{
		inode->i_op = &myfs_file_inode_operations;
		inode->i_fop = &myfs_file_operations;
	}
	else
		if(S_ISDIR(inode->i_mode))
		{
			inode->i_op = &myfs_dir_inode_operations;
			inode->i_fop = &myfs_dir_operations;
		}
		else
			if(S_ISLNK(inode->i_mode))
			{
				inode->i_op = &myfs_symlink_inode_operations;
			}

	inode->i_mapping->a_ops = &myfs_aops;

	brelse(bh);
	unlock_new_inode(inode);

	return inode;
}

/*!
	функция читает с диска и возвращает и-узел файловой системы myfs,
	представленный структурой myfs_inode
*/
static struct myfs_inode *myfs_get_inode(struct super_block *sb, unsigned int ino, struct buffer_head **bhp)				
{
	struct buffer_head *bh;
	unsigned int offset;
	unsigned int block;
	unsigned int inodes_per_block;
	
	struct myfs_super_block *myfs_sb = MYFS_SB(sb)->s_mfs;
	*bhp = NULL;

	inodes_per_block = sb->s_blocksize/(sizeof(struct myfs_inode));

	block = (ino - myfs_sb->s_first_nonmeta_inode) / inodes_per_block;
	offset = (ino - myfs_sb->s_first_nonmeta_inode) * sizeof(struct myfs_inode);

	bh = sb_bread(sb, block + 3);
	if (!bh) {
		printk(KERN_ALERT "myfs: unable to read inode block (%d).\n", block+3);
		return NULL;
	}
	*bhp = bh;
	return (struct myfs_inode *)(bh->b_data + offset);
}

int __myfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep,	void **fsdata)
{
	return block_write_begin(file, mapping, pos, len, flags, pagep, fsdata, myfs_get_block);
}

/*int myfs_prepare_chunk(struct page *page, loff_t pos, unsigned len)
{
	return __block_write_begin(page, pos, len, myfs_get_block);
}*/

int myfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata)
{
	struct inode *inode;
	*pagep = NULL;
	if (file) {
		inode = file->f_path.dentry->d_inode;
		printk(KERN_ALERT "myfs: filesize = %lld, pos = %lld, len = %u.\n", inode->i_size, pos, len);
		if ((pos + len) <= inode->i_sb->s_blocksize)
			return __myfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
		else return -ENOSPC;
	}
	else
		return __myfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
}

/*!
	функция записи страницы
*/
static int myfs_writepage(struct page *page, struct writeback_control *wbc)
{
	return block_write_full_page(page, myfs_get_block, wbc);
}

/*!
	вспомогательная функция, которую вызывает функция myfs_write_inode ддя
	синхронизации и-узла находящегося на диске с тем, что находится в памяти
*/
static int myfs_update_inode(struct inode *inode)
{
	struct myfs_inode_info *msi = MYFS_I(inode);
	struct super_block *sb = inode->i_sb;
	unsigned int ino = inode->i_ino;
	struct buffer_head *bh;
	struct myfs_inode *raw = myfs_get_inode(sb, ino, &bh);
	int err = 0;

	if (!raw || IS_ERR(raw))
		return -EIO;

	/* обновляем поля и-узла, находящегося на диске, полями и-узла, находящегося в памяти */
	printk(KERN_ALERT "myfs: inode (%lu) size = %lld , mode = 0x%x.\n",inode->i_ino, inode->i_size, inode->i_mode);
	raw->i_size = cpu_to_le32(inode->i_size);
	raw->atime = (inode->i_atime);
	raw->mtime = (inode->i_mtime);
	raw->ctime = (inode->i_ctime);
	raw->i_num_links = cpu_to_le32(inode->i_nlink);
	raw->i_gid = cpu_to_le32(inode->i_gid);
	raw->i_uid = cpu_to_le32(inode->i_uid);
	raw->i_mode = cpu_to_le32(inode->i_mode);
	printk(KERN_ALERT "myfs: data block = %u.\n",msi->i_data[0]);
	raw->data[0] = msi->i_data[0];

	mark_buffer_dirty(bh);
	sync_dirty_buffer(bh);
	if (buffer_req(bh) && !buffer_uptodate(bh))
	{
		printk(KERN_ALERT "myfs: I/O error while syncing inode to disk\n");
		err = -EIO;
	}
	brelse(bh);
	return err;
}

/*!
	функция удаления и-узла
*/
void myfs_delete_inode(struct inode *inode)
{
	truncate_inode_pages(&inode->i_data, 0);
	if(is_bad_inode(inode))
	{
		goto no_delete;
	}
	mark_inode_dirty(inode);
	myfs_update_inode(inode);
	inode->i_size = 0;
	myfs_free_inode(inode);

	return;

no_delete:
	clear_inode(inode);
}

/*!
	функция синхронизации и-узла находящегося на диске с тем, что находится в памяти
*/
int myfs_write_inode(struct inode *inode, int wait)
{
	return myfs_update_inode(inode);
}

/*!
	функция устанавливает указатели VFS и-узла на соответствующие структуры,
	содержащие ссылки на необходимые и-узлу функции в соответствии с его типом
*/
void myfs_set_inode(struct inode *inode, dev_t rdev)
{
	if (S_ISREG(inode->i_mode))
	{
		inode->i_op = &myfs_file_inode_operations;
		inode->i_fop = &myfs_file_operations;
		inode->i_mapping->a_ops = &myfs_aops;
	} 
	else 
		if (S_ISDIR(inode->i_mode))
		{
			inode->i_op = &myfs_dir_inode_operations;
			inode->i_fop = &myfs_dir_operations;
			inode->i_mapping->a_ops = &myfs_aops;
		}
		else 
			if (S_ISLNK(inode->i_mode))
			{
				inode->i_op = &myfs_symlink_inode_operations;
				inode->i_mapping->a_ops = &myfs_aops;
			}
}

/*!
	данная структура содержит ссылки на функции, необходимые для работы с адрессным пространством
*/
const struct address_space_operations myfs_aops = {
	.readpage = myfs_readpage,
	//.readpages = myfs_readpages,
	.writepage = myfs_writepage,
	.write_begin = myfs_write_begin,
	.write_end = generic_write_end,
	.sync_page = block_sync_page,
};
